#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <time.h>
#include "parse.h"
#include "respond.h"
#include "log.h"

#define ECHO_PORT 9999
#define BUF_SIZE 9999
#define MAX_CONNECTIONS 5

char PATH0[4196];
char PATH[4196] = "./static_site";

int close_socket(int sock) {
    if (close(sock)) {
        fprintf(stderr, "Failed closing socket.\n");
        return 1;
    }
    return 0;
}

int create_and_bind() {
    int sock;
    struct sockaddr_in addr;

    if ((sock = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
        fprintf(stderr, "Failed creating socket.\n");
        return -1;
    }

    addr.sin_family = AF_INET;
    addr.sin_port = htons(ECHO_PORT);
    addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(sock, (struct sockaddr *)&addr, sizeof(addr))) {
        close_socket(sock);
        fprintf(stderr, "Failed binding socket.\n");
        return -1;
    }

    return sock;
}

int listening(int sock) {
    if (listen(sock, MAX_CONNECTIONS) == -1) {
        close_socket(sock);
        fprintf(stderr, "Error listening on socket.\n");
        return 0;
    }
    return 1;
}

void handle_client(int sock) {
    int client_sock;
    ssize_t readret;
    socklen_t cli_size;
    struct sockaddr_in cli_addr;
    char buf[BUF_SIZE];
    struct timeval tv;
    char timestamp[32];

    while (1) {
        cli_size = sizeof(cli_addr);
        if ((client_sock = accept(sock, (struct sockaddr *)&cli_addr, &cli_size)) == -1) {
            close(sock);
            fprintf(stderr, "Error accepting connection.\n");
            exit(EXIT_FAILURE);
        }

        gettimeofday(&tv, NULL);
        strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", localtime(&tv.tv_sec));

        readret = 0;
        memset(buf, 0, BUF_SIZE);
        while ((readret = recv(client_sock, buf, BUF_SIZE, 0)) >= 1) {
            Respond *respond = init_respond();
            Request *request = parse(buf, readret, client_sock);

            if (request == NULL) {
                respond_400(respond);
                respond2buf(respond, buf);
                send(client_sock, buf, strlen(buf), 0);
                LOG_PRINT(LOG_ERROR, " [client %d]\r\n\t[400 Bad request] The response content could not be parsed correctly", client_sock);
                LOG_Destroy();
                memset(buf, 0, BUF_SIZE);
                free_respond(respond);
                continue;
            }

            if (match_version(request->http_version)) {
                respond_505(respond);
                respond2buf(respond, buf);
                send(client_sock, buf, strlen(buf), 0);
                LOG_PRINT(LOG_ERROR, " [client %d]\r\n\t[505 HTTP Version not supported] The currently entered protocol is %s, not HTTP/1.1", client_sock, request->http_version);
                LOG_Destroy();
                memset(buf, 0, BUF_SIZE);
                free_request(request);
                free_respond(respond);
                continue;
            }

            strcpy(PATH0, PATH);
            if (strcmp(request->http_uri, "/") == 0) {
                memset(request->http_uri, 0, 4096);
                strcpy(request->http_uri, "/index.html");
            }
            strcat(PATH0, request->http_uri);

            int plan = process_method(request->http_method);
            switch (plan) {
                case STATE_GET: {
                    if (file_status(PATH0, client_sock)) {
                        respond_404(respond);
                        respond2buf(respond, buf);
                        send(client_sock, buf, strlen(buf), 0);
                        free_request(request);
                        free_respond(respond);
                        break;
                    }
                    respond_GET_200(respond, PATH0);
                    respond2buf(respond, buf);
                    send(client_sock, buf, strlen(buf), 0);
                    LOG_PRINT(LOG_ACTION, " [client %d] request: [%s %s %s] respond: [HTTP/1.1 200 OK] %d", client_sock, request->http_method, request->http_uri, request->http_version, respond->content_length);
                    LOG_Destroy();
                    free_request(request);
                    free_respond(respond);
                    break;
                }
                case STATE_HEAD: {
                    if (file_status(PATH0, client_sock)) {
                        respond_404(respond);
                        respond2buf(respond, buf);
                        send(client_sock, buf, strlen(buf), 0);
                        free_request(request);
                        free_respond(respond);
                        break;
                    }
                    respond_HEAD_200(respond, PATH0);
                    respond2buf(respond, buf);
                    send(client_sock, buf, strlen(buf), 0);
                    LOG_PRINT(LOG_ACTION, " [client %d] request: [%s %s %s] respond: [HTTP/1.1 200 OK] %d", client_sock, request->http_method, request->http_uri, request->http_version, respond->content_length);
                    LOG_Destroy();
                    free_request(request);
                    free_respond(respond);
                    break;
                }
                case STATE_POST: {
                    ssize_t sendret = send(client_sock, buf, readret, 0);
                    if (sendret != readret) {
                        close_socket(client_sock);
                        close_socket(sock);
                        LOG_ERR("%s", "Error sending to client.");
                        exit(EXIT_FAILURE);
                    }
                    free_request(request);
                    break;
                }
                case STATE_UNKNOW:
                default: {
                    respond_501(respond);
                    respond2buf(respond, buf);
                    send(client_sock, buf, strlen(buf), 0);
                    LOG_PRINT(LOG_ERROR, " [client %d]\r\n\t[501 Not Implemented] The currently entered request is %s, not yet implemented", client_sock, request->http_method);
                    LOG_Destroy();
                    free_request(request);
                    free_respond(respond);
                    break;
                }
            }
            memset(buf, 0, BUF_SIZE);
        }

        if (readret == -1) {
            close_socket(client_sock);
            close_socket(sock);
            LOG_ERR("%s", "Error reading from client socket.");
            exit(EXIT_FAILURE);
        }

        if (close_socket(client_sock)) {
            close_socket(sock);
            LOG_ERR("%s", "Error closing client socket.");
            exit(EXIT_FAILURE);
        }
    }
}

int main(int argc, char *argv[]) {
    LOG_SetPrintLogPlaceFlag(1);
    LOG_Init("run", 8000);
    int sock;

    fprintf(stdout, "----- Echo Server -----\n");

    sock = create_and_bind();
    if (sock == -1) {
        exit(EXIT_FAILURE);
    }

    if (!listening(sock)) {
        exit(EXIT_FAILURE);
    }

    handle_client(sock);

    close_socket(sock);

    return EXIT_SUCCESS;
}